/*******************************************************************************
 * Copyright (c) 2016 Avaloq Group AG and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Avaloq Group AG - initial API and implementation
 *******************************************************************************/
package com.avaloq.tools.ddk.xtext.naming;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.util.IResourceScopeCache;
import org.eclipse.xtext.util.Tuples;

import com.google.inject.Inject;


/**
 * Abstract implementation of name provider used by the resource description to determine the name of a given object that shall be
 * exported.
 * <p>
 * Subclasses will automatically be generated by the build fragment and can further be subclassed.
 */
public abstract class AbstractExportedNameProvider extends IQualifiedNameProvider.AbstractImpl {

  @Inject
  private static final IResourceScopeCache CACHE = IResourceScopeCache.NullImpl.INSTANCE;

  @Inject
  private final IQualifiedNameConverter converter = new IQualifiedNameConverter.DefaultImpl();

  /**
   * {@inheritDoc} The qualified name is computed by delegating to {@link #qualifiedName(EObject)}. The results are cached.
   */
  @Override
  public QualifiedName getFullyQualifiedName(final EObject obj) {
    return CACHE.get(Tuples.pair(obj, this.getClass()), obj.eResource(), () -> qualifiedName(obj));
  }

  /**
   * Abstract method which computes the qualified name for a given object.
   *
   * @param obj
   *          context object
   * @return qualified name
   */
  protected abstract QualifiedName qualifiedName(EObject obj);

  /**
   * Qualifies a simple name with that of the container (if any).
   *
   * @param obj
   *          context object
   * @param name
   *          simple name of object
   * @return qualified name of object
   */
  protected QualifiedName qualifyWithContainerName(final EObject obj, final String name) {
    return qualifyWithContainerName(obj, getConverter().toQualifiedName(name));
  }

  /**
   * Qualifies a simple name with that of the container (if any).
   *
   * @param obj
   *          context object
   * @param name
   *          simple name of object
   * @return qualified name of object
   */
  protected QualifiedName qualifyWithContainerName(final EObject obj, final QualifiedName name) {
    final QualifiedName parentName = getContainerQualifiedName(obj);
    if (parentName != null && !parentName.isEmpty()) {
      return parentName.append(name);
    }
    return name;
  }

  /**
   * Get the fully qualified name of an object's container. The operation returns the qualified name of the innermost container
   * that does indeed have a qualified name. See also {@link getQualifiedName}.
   *
   * @param obj
   *          The EObject
   * @return The fully qualified name of the container of {@link obj}, or null.
   */
  public QualifiedName getContainerQualifiedName(final EObject obj) {
    EObject container = obj.eContainer();
    while (container != null) {
      final QualifiedName containerName = getFullyQualifiedName(container);
      if (containerName != null) {
        return containerName;
      }
      container = container.eContainer();
    }
    return null;
  }

  protected IQualifiedNameConverter getConverter() {
    return converter;
  }
}
